{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "import collections\n",
    "\n",
    "Card = collections.namedtuple('Card', ['rank', 'suit'])\n",
    "\n",
    "class FrenchDeck:\n",
    "    ranks = [str(n) for n in range(2, 11)] + list('JQKA')\n",
    "    suits = 'spades diamonds clubs hearts'.split()\n",
    "\n",
    "    def __init__(self):\n",
    "        self._cards = [Card(rank, suit) for suit in self.suits\n",
    "                                        for rank in self.ranks]\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self._cards)\n",
    "\n",
    "    def __getitem__(self, position):\n",
    "        return self._cards[position]\n",
    "    \n",
    "    def __setitem__(self, position, card):\n",
    "        self._cards[position] = card"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "deck = FrenchDeck()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "random.shuffle(deck)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Card(rank='10', suit='hearts'),\n",
       " Card(rank='6', suit='clubs'),\n",
       " Card(rank='4', suit='diamonds'),\n",
       " Card(rank='6', suit='hearts'),\n",
       " Card(rank='J', suit='hearts'),\n",
       " Card(rank='10', suit='diamonds'),\n",
       " Card(rank='9', suit='diamonds'),\n",
       " Card(rank='8', suit='spades'),\n",
       " Card(rank='9', suit='hearts'),\n",
       " Card(rank='9', suit='spades'),\n",
       " Card(rank='2', suit='spades'),\n",
       " Card(rank='J', suit='clubs'),\n",
       " Card(rank='8', suit='hearts'),\n",
       " Card(rank='8', suit='clubs'),\n",
       " Card(rank='A', suit='diamonds'),\n",
       " Card(rank='5', suit='hearts'),\n",
       " Card(rank='A', suit='hearts'),\n",
       " Card(rank='K', suit='hearts'),\n",
       " Card(rank='Q', suit='diamonds'),\n",
       " Card(rank='K', suit='diamonds'),\n",
       " Card(rank='3', suit='hearts'),\n",
       " Card(rank='5', suit='clubs'),\n",
       " Card(rank='10', suit='spades'),\n",
       " Card(rank='2', suit='clubs'),\n",
       " Card(rank='J', suit='spades'),\n",
       " Card(rank='Q', suit='clubs'),\n",
       " Card(rank='6', suit='diamonds'),\n",
       " Card(rank='3', suit='spades'),\n",
       " Card(rank='6', suit='spades'),\n",
       " Card(rank='7', suit='spades'),\n",
       " Card(rank='K', suit='clubs'),\n",
       " Card(rank='Q', suit='hearts'),\n",
       " Card(rank='4', suit='clubs'),\n",
       " Card(rank='2', suit='diamonds'),\n",
       " Card(rank='7', suit='clubs'),\n",
       " Card(rank='Q', suit='spades'),\n",
       " Card(rank='3', suit='diamonds'),\n",
       " Card(rank='7', suit='diamonds'),\n",
       " Card(rank='9', suit='clubs'),\n",
       " Card(rank='7', suit='hearts'),\n",
       " Card(rank='4', suit='hearts'),\n",
       " Card(rank='4', suit='spades'),\n",
       " Card(rank='8', suit='diamonds'),\n",
       " Card(rank='5', suit='diamonds'),\n",
       " Card(rank='5', suit='spades'),\n",
       " Card(rank='2', suit='hearts'),\n",
       " Card(rank='3', suit='clubs'),\n",
       " Card(rank='10', suit='clubs'),\n",
       " Card(rank='A', suit='clubs'),\n",
       " Card(rank='J', suit='diamonds'),\n",
       " Card(rank='A', suit='spades'),\n",
       " Card(rank='K', suit='spades')]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "deck._cards"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "import collections\n",
    "\n",
    "Card = collections.namedtuple('Card', ['rank', 'suit'])\n",
    "\n",
    "class FrenchDeck2(collections.MutableSequence):\n",
    "    ranks = [str(n) for n in range(2, 11)] + list('JQKA')\n",
    "    suits = 'spades diamonds clubs hearts'.split()\n",
    "\n",
    "    def __init__(self):\n",
    "        self._cards = [Card(rank, suit) for suit in self.suits\n",
    "                                        for rank in self.ranks]\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self._cards)\n",
    "\n",
    "    def __getitem__(self, position):\n",
    "        return self._cards[position]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'FrenchDeck2' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-5-a66ceec541c4>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mFrenchDeck2\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m: name 'FrenchDeck2' is not defined"
     ]
    }
   ],
   "source": [
    "FrenchDeck2()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 抽象基类 Tombala.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import abc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Tombola(abc.ABC):  # <1>\n",
    "\n",
    "    @abc.abstractmethod\n",
    "    def load(self, iterable):  # <2>\n",
    "        \"\"\"Add items from an iterable.\"\"\"\n",
    "\n",
    "    @abc.abstractmethod\n",
    "    def pick(self):  # <3>\n",
    "        \"\"\"Remove item at random, returning it.\n",
    "        This method should raise `LookupError` when the instance is empty.\n",
    "        \"\"\"\n",
    "\n",
    "    def loaded(self):  # <4>\n",
    "        \"\"\"Return `True` if there's at least 1 item, `False` otherwise.\"\"\"\n",
    "        return bool(self.inspect())  # <5>\n",
    "\n",
    "\n",
    "    def inspect(self):\n",
    "        \"\"\"Return a sorted tuple with the items currently inside.\"\"\"\n",
    "        items = []\n",
    "        while True:  # <6>\n",
    "            try:\n",
    "                items.append(self.pick())\n",
    "            except LookupError:\n",
    "                break\n",
    "        self.load(items)  # <7>\n",
    "        return tuple(sorted(items))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "import os"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### bingo.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "class BingoCage(Tombola):  # <1>\n",
    "\n",
    "    def __init__(self, items):\n",
    "        self._randomizer = random.SystemRandom()  # <2>\n",
    "        self._items = []\n",
    "        self.load(items)  # <3>\n",
    "\n",
    "    def load(self, items):\n",
    "        self._items.extend(items)\n",
    "        self._randomizer.shuffle(self._items)  # <4>\n",
    "\n",
    "    def pick(self):  # <5>\n",
    "        try:\n",
    "            return self._items.pop()\n",
    "        except IndexError:\n",
    "            raise LookupError('pick from empty BingoCage')\n",
    "\n",
    "    def __call__(self):  # <7>\n",
    "        self.pick()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### lotto.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "class LotteryBlower(Tombola):\n",
    "\n",
    "    def __init__(self, iterable):\n",
    "        self._balls = list(iterable)  # <1>\n",
    "\n",
    "    def load(self, iterable):\n",
    "        self._balls.extend(iterable)\n",
    "\n",
    "    def pick(self):\n",
    "        try:\n",
    "            position = random.randrange(len(self._balls))  # <2>\n",
    "        except ValueError:\n",
    "            raise LookupError('pick from empty BingoCage')\n",
    "        return self._balls.pop(position)  # <3>\n",
    "\n",
    "    def loaded(self):  # <4>\n",
    "        return bool(self._balls)\n",
    "\n",
    "    def inspect(self):  # <5>\n",
    "        return tuple(sorted(self._balls))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### drum.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "class TumblingDrum(Tombola):\n",
    "\n",
    "    def __init__(self, iterable):\n",
    "        self._balls = []\n",
    "        self.load(iterable)\n",
    "\n",
    "    def load(self, iterable):\n",
    "        self._balls.extend(iterable)\n",
    "        shuffle(self._balls)\n",
    "\n",
    "    def pick(self):\n",
    "        return self._balls.pop()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### tombolist.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "@Tombola.register\n",
    "class TomboList(list):\n",
    "    \n",
    "    def pick(self):\n",
    "        try:\n",
    "            pos = random.randrange(len(self))\n",
    "            return self.pop(pos)\n",
    "        except ValueError:\n",
    "            LookupError('pop from empty TomboList')\n",
    "            \n",
    "    load = list.extend\n",
    "    \n",
    "    def loaded(self):\n",
    "        return bool(self)\n",
    "    \n",
    "    def inspect(self):\n",
    "        return tuple(sorted(self))\n",
    "    \n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "issubclass(TomboList, Tombola)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "t = TomboList(range(100))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "isinstance(t, Tombola)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(__main__.TomboList, list, object)"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "TomboList.__mro__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### tombola_runner.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "TEST_FILE = 'tombola_tests.rst'\n",
    "TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}'\n",
    "\n",
    "\n",
    "def main(argv):\n",
    "    verbose = '-v' in argv\n",
    "    real_subclasses = Tombola.__subclasses__()  # <2>\n",
    "    virtual_subclasses = list(Tombola._abc_registry)  # <3>\n",
    "\n",
    "    for cls in real_subclasses + virtual_subclasses:  # <4>\n",
    "        test(cls, verbose)\n",
    "\n",
    "\n",
    "def test(cls, verbose=False):\n",
    "\n",
    "    res = doctest.testfile(\n",
    "            TEST_FILE,\n",
    "            globs={'ConcreteTombola': cls},  # <5>\n",
    "            verbose=verbose,\n",
    "            optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)\n",
    "    tag = 'FAIL' if res.failed else 'OK'\n",
    "    print(TEST_MSG.format(cls.__name__, res, tag))  # <6>\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    import sys\n",
    "    main(sys.argv)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'bingo' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-51-7edb94d42754>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mbingo\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlotto\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtombolist\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdrum\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m: name 'bingo' is not defined"
     ]
    }
   ],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
